#include <crt_util.h>

#if (TARGET_OS == OS_WIN)

int _rwlock_init(_rwlock_t* lock)
{
    rc_error(lock != NULL, S_ERROR);
    /* Initialize the semaphore that acts as the write lock. */
    HANDLE handle = CreateSemaphoreW(NULL, 1, 1, NULL);
    if (handle == NULL)
        return -1;

    lock->write_semaphore_ = handle;
    /* Initialize the critical section protecting the reader count. */
    InitializeCriticalSection(&lock->num_readers_lock_);
    /* Initialize the reader count. */
    lock->num_readers_ = 0;

    return 0;
}

int _rwlock_destroy(_rwlock_t* lock)
{
    rc_error(lock != NULL, S_ERROR);
    DeleteCriticalSection(&lock->num_readers_lock_);
    CloseHandle(lock->write_semaphore_);
    return S_SUCCESS;
}

int _rwlock_rdlock(_rwlock_t* lock)
{
    rc_error(lock != NULL, S_ERROR);
    /* Acquire the lock that protects the reader count. */
    EnterCriticalSection(&lock->num_readers_lock_);

    int err = S_SUCCESS;
    /* Increase the reader count, and lock for write if this is the first
     * reader.
     */
    if (++lock->num_readers_ == 1) {
        DWORD r = WaitForSingleObject(lock->write_semaphore_, INFINITE);
        if (r != WAIT_OBJECT_0) {
            crt_setfatalerror(crt_geterror());
            err = S_ERROR;
        }
    }

    /* Release the lock that protects the reader count. */
    LeaveCriticalSection(&lock->num_readers_lock_);
    return err;
}

int _rwlock_tryrdlock(_rwlock_t* lock)
{
    rc_error(lock != NULL, S_ERROR);
    int err = S_SUCCESS;

    if (!TryEnterCriticalSection(&lock->num_readers_lock_))
        return S_ERROR;

    if (lock->num_readers_ == 0) {
        /* Currently there are no other readers, which means that the write lock
         * needs to be acquired.
         */
        DWORD r = WaitForSingleObject(lock->write_semaphore_, 0);
        if (r == WAIT_OBJECT_0)
            lock->num_readers_++;
        else if (r == WAIT_TIMEOUT)
            err = S_ERROR;
        else if (r == WAIT_FAILED) {
            crt_setfatalerror(crt_geterror());
            err = S_ERROR;
        }

    } else {
        /* The write lock has already been acquired because there are other
         * active readers.
         */
        lock->num_readers_++;
    }

    LeaveCriticalSection(&lock->num_readers_lock_);
    return err;
}

int _rwlock_rdunlock(_rwlock_t* lock)
{
    rc_error(lock != NULL, S_ERROR);
    int err = S_SUCCESS;
    EnterCriticalSection(&lock->num_readers_lock_);

    if (--lock->num_readers_ == 0) {
        if (!ReleaseSemaphore(lock->write_semaphore_, 1, NULL)) {
            crt_setfatalerror(crt_geterror());
            err = S_ERROR;
        }
    }

    LeaveCriticalSection(&lock->num_readers_lock_);

    return err;
}

int _rwlock_wrlock(_rwlock_t* lock)
{
    rc_error(lock != NULL, S_ERROR);
    DWORD r = WaitForSingleObject(lock->write_semaphore_, INFINITE);
    if (r != WAIT_OBJECT_0) {
        crt_setfatalerror(crt_geterror());
        return S_ERROR;
    }
    return S_SUCCESS;
}

int _rwlock_trywrlock(_rwlock_t* lock)
{
    rc_error(lock != NULL, S_ERROR);
    DWORD r = WaitForSingleObject(lock->write_semaphore_, 0);
    if (r == WAIT_OBJECT_0)
        return S_SUCCESS;
    else if (r == WAIT_TIMEOUT)
        return S_ERROR;
    else
        crt_setfatalerror(crt_geterror());

    return S_ERROR;
}

int _rwlock_wrunlock(_rwlock_t* lock)
{
    rc_error(lock != NULL, S_ERROR);
    if (!ReleaseSemaphore(lock->write_semaphore_, 1, NULL)) {
        crt_setfatalerror(crt_geterror());
        return S_ERROR;
    }
    return S_SUCCESS;
}

#elif (TARGET_OS == OS_POSIX)

int _rwlock_init(_rwlock_t* lock)
{
    rc_error(lock != NULL, S_ERROR);
    if (pthread_rwlock_init(&lock->rwlock, NULL)) {
        return S_ERROR;
    }

    return S_SUCCESS;
}

int _rwlock_destroy(_rwlock_t* lock)
{
    rc_error(lock != NULL, S_ERROR);
    if (pthread_rwlock_destroy(&lock->rwlock)) {
        return S_ERROR;
    }

    return S_SUCCESS;
}

int _rwlock_rdlock(_rwlock_t* lock)
{
    rc_error(lock != NULL, S_ERROR);
    if (pthread_rwlock_rdlock(&lock->rwlock)) {
        return S_ERROR;
    }
    return S_SUCCESS;
}

int _rwlock_tryrdlock(_rwlock_t* lock)
{
    rc_error(lock != NULL, S_ERROR);
    if (pthread_rwlock_tryrdlock(&lock->rwlock)) {
        return S_ERROR;
    }

    return S_SUCCESS;
}

int _rwlock_rdunlock(_rwlock_t* lock)
{
    rc_error(lock != NULL, S_ERROR);
    if (pthread_rwlock_unlock(&lock->rwlock)) {
        return S_ERROR;
    }

    return S_SUCCESS;
}

int _rwlock_wrlock(_rwlock_t* lock)
{
    rc_error(lock != NULL, S_ERROR);
    if (pthread_rwlock_wrlock(&lock->rwlock)) {
        return S_ERROR;
    }

    return S_SUCCESS;
}

int _rwlock_trywrlock(_rwlock_t* lock)
{
    rc_error(lock != NULL, S_ERROR);
    if (pthread_rwlock_trywrlock(&lock->rwlock)) {
        return S_ERROR;
    }

    return S_SUCCESS;
}

int _rwlock_wrunlock(_rwlock_t* lock)
{
    rc_error(lock != NULL, S_ERROR);
    if (pthread_rwlock_unlock(&lock->rwlock)) {
        return S_ERROR;
    }

    return S_SUCCESS;
}

#elif (TARGET_OS == OS_MACH)

int _rwlock_init(_rwlock_t* lock)
{
    rc_error(lock != NULL, S_ERROR);
    if (pthread_rwlock_init(&lock->rwlock, NULL)) {
        return S_ERROR;
    }

    return S_SUCCESS;
}

int _rwlock_destroy(_rwlock_t* lock)
{
    rc_error(lock != NULL, S_ERROR);
    if (pthread_rwlock_destroy(&lock->rwlock)) {
        return S_ERROR;
    }

    return S_SUCCESS;
}

int _rwlock_rdlock(_rwlock_t* lock)
{
    rc_error(lock != NULL, S_ERROR);
    if (pthread_rwlock_rdlock(&lock->rwlock)) {
        return S_ERROR;
    }

    return S_SUCCESS;
}

int _rwlock_tryrdlock(_rwlock_t* lock)
{
    rc_error(lock != NULL, S_ERROR);
    if (pthread_rwlock_tryrdlock(&lock->rwlock)) {
        return S_ERROR;
    }

    return S_SUCCESS;
}

int _rwlock_rdunlock(_rwlock_t* lock)
{
    rc_error(lock != NULL, S_ERROR);
    if (pthread_rwlock_unlock(&lock->rwlock)) {
        return S_ERROR;
    }

    return S_SUCCESS;
}

int _rwlock_wrlock(_rwlock_t* lock)
{
    rc_error(lock != NULL, S_ERROR);
    if (pthread_rwlock_wrlock(&lock->rwlock)) {
        return S_ERROR;
    }

    return S_SUCCESS;
}

int _rwlock_trywrlock(_rwlock_t* lock)
{
    rc_error(lock != NULL, S_ERROR);
    if (pthread_rwlock_trywrlock(&lock->rwlock)) {
        return S_ERROR;
    }
    return S_SUCCESS;
}

int _rwlock_wrunlock(_rwlock_t* lock)
{
    rc_error(lock != NULL, S_ERROR);
    if (pthread_rwlock_unlock(&lock->rwlock)) {
        return S_ERROR;
    }
    return S_SUCCESS;
}

#elif (TARGET_OS == OS_UNIX)

int _rwlock_init(_rwlock_t* lock)
{
    rc_error(lock != NULL, S_ERROR);
    if (pthread_rwlock_init(&lock->rwlock, NULL)) {
        return S_ERROR;
    }

    return S_SUCCESS;
}

int _rwlock_destroy(_rwlock_t* lock)
{
    rc_error(lock != NULL, S_ERROR);
    if (pthread_rwlock_destroy(&lock->rwlock)) {
        return S_ERROR;
    }

    return S_SUCCESS;
}

int _rwlock_rdlock(_rwlock_t* lock)
{
    rc_error(lock != NULL, S_ERROR);
    if (pthread_rwlock_rdlock(&lock->rwlock)) {
        return S_ERROR;
    }
    return S_SUCCESS;
}

int _rwlock_tryrdlock(_rwlock_t* lock)
{
    rc_error(lock != NULL, S_ERROR);
    if (pthread_rwlock_tryrdlock(&lock->rwlock)) {
        return S_ERROR;
    }

    return S_SUCCESS;
}

int _rwlock_rdunlock(_rwlock_t* lock)
{
    rc_error(lock != NULL, S_ERROR);
    if (pthread_rwlock_unlock(&lock->rwlock)) {
        return S_ERROR;
    }

    return S_SUCCESS;
}

int _rwlock_wrlock(_rwlock_t* lock)
{
    rc_error(lock != NULL, S_ERROR);
    if (pthread_rwlock_wrlock(&lock->rwlock)) {
        return S_ERROR;
    }

    return S_SUCCESS;
}

int _rwlock_trywrlock(_rwlock_t* lock)
{
    rc_error(lock != NULL, S_ERROR);
    if (pthread_rwlock_trywrlock(&lock->rwlock)) {
        return S_ERROR;
    }

    return S_SUCCESS;
}

int _rwlock_wrunlock(_rwlock_t* lock)
{
    rc_error(lock != NULL, S_ERROR);
    if (pthread_rwlock_unlock(&lock->rwlock)) {
        return S_ERROR;
    }

    return S_SUCCESS;
}

#endif
